// ==UserScript== // @name 💡WebPreview - 信息直达 // @namespace https://ez118.github.io/ // @version 1.5.1 // @description 支持国内主流搜索引擎的搜索结果快速预览(直达网页大纲)。只需点击搜索结果旁的小灯泡按钮,即可在右侧速览窗中快速查看目标网站所含的图片、链接、标题大纲、文本。 // @author ZZY_WISU // @match *://*/* // @connect * // @license GNU GPLv3 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAMAAAApB0NrAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAADZQTFRFAAAAn8LgEG6+SZDMg7HaZaDSgrHZkLrdO4jJZqHTgbLZHnbCdKnWLX/FV5jPLX7FSY/LdKnVpsJ+6QAAABJ0Uk5TACb//8j/yoP//8n/8f/////yflwd4QAAAORJREFUeJy1k9EagyAIhfVYGqnV3v9lp2kLjdrFvnGT4Q8coJTqTMP0rov9nRnYebTn2Z3HCSSGzvAsvwjNACuQIH1lGiRBNU+IALwpAXq4hCUCh0VR3y69ykrZbqCFdTHJUNLd5JRGGeH4q4fAdNkDwgWxWLqY6eSJKBcZYTtmz5tuSa1pGkZkUBrJT621WL/U0vW6+nyDkLRm3/YOqXfXeKP8SRGb0M0u+AV14poCZUlaFNMK3YQ9fGwtjLjPwzaYPPynUomp9shQHv4XZl/Oz8yCGGwwj4yKRbL08zGz48t1rjcZWATm0KKJdwAAAABJRU5ErkJggg== // @run-at document-end // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant window.onurlchange // @require https://update.greasyfork.org/scripts/499192/1402326/jquery_360.js // ==/UserScript== var iconImg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAUCAMAAACK2/weAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAHtQTFRFAAAAOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGofOGoffJU8/QAAACl0Uk5TABWJvOy7hhRd8P/tT25TIfzzlXziyPju8c/NgHgY9OMRZvU2WkPQx8o0T8eSAAAAbUlEQVR4nI3OyQ6CQBBF0auASOOEgICoCKLw/19ImqG7TFx4V3UWLymA1dpxXW/jM7YN1Fi409or0wGOJ8vI56xEMYlkykUyI5cMKCSvlJI37g+r6gm1UfPSb7UL39PTC/nJz6SOv/qazuNeXwMWhAmHAXFPJgAAAABJRU5ErkJggg=="; /* 用于存储小灯泡按钮的图片数据 */ const contentEleSelList = { "blog.csdn.net": "#article_content", "zhuanlan.zhihu.com": ".Post-RichTextContainer", "jingyan.baidu.com": "#format-exp", "www.bilibili.com": "#article-content", "zhidao.baidu.com": "#qb-content", "www.cnblogs.com": "#topics", "www.sohu.com": "#mp-editor" }; /* 用于储存指定网站的内容所在父元素(特定博客网站内容优化) */ const VideoSupport = [ ["https://v.youku.com/v_show/*.html", "https://player.youku.com/embed/*"], ["https://v.qq.com/x/page/*.html", "https://v.qq.com/txp/iframe/player.html?vid=*"], ["https://www.bilibili.com/video/BV*/", "https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=*"], ["https://www.bilibili.com/video/av*/", "https://www.bilibili.com/blackboard/html5mobileplayer.html?aid=*"] ]; /* 用于存储阅读器支持直接播放视频的网站及其嵌入播放器代码 */ /* ================[ 文章大纲提取脚本 ]================== */ /* 生成随机字符串 */ function randomString(len) { len = len || 32; var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; var maxPos = $chars.length; var pwd = ''; for (let i = 0; i < len; i++) { pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); } return pwd; } /* 标题元素 */ function TitleElement(tag, title, level, id) { this.tag = tag; this.title = title; this.level = level; this.id = id; } /* 是否是标题元素 */ function isTitleTag(tag) { return tag.is("h1, h2, h3, h4, h5, h6, h7"); } /* 生成大纲 */ function getOutline($articleContent) { /* 全部元素 */ var $eles = $($articleContent).find("*"); /* 标题元素列表 */ var titleElementArr = new Array(); /* 上一个元素 */ var preTitleElement = null; $.each($eles, function(index, item) { if (isTitleTag($(item))) { var id = randomString(20); var level = 1; var tag = parseInt($(item).get(0).tagName.replace('h', "").replace('H', "")); var title = $(item).text(); if (null != preTitleElement) { var tagPre = preTitleElement.tag; var levelPre = preTitleElement.level; if (tagPre > tag) { level = levelPre - 1; } else if (tagPre < tag) { level = levelPre + 1; } else { level = levelPre; } } if (title.trim().length > 0) { $(item).attr("id", id); var titleElement = new TitleElement(tag, title, level, id); titleElementArr.push(titleElement); preTitleElement = titleElement; } } }); return titleElementArr; } /* ============================================= */ function runAsync(url,send_type,data_ry) { var p = new Promise((resolve, reject)=> { GM_xmlhttpRequest({ method: send_type, url: url, headers: {"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"}, data: data_ry, onload: function(response){resolve(response.responseText);}, onerror: function(response){reject("请求失败");} }); }); return p; } function JudgeVideoSupport(url) { var previewFlag = 0; /* 判断是否为支持预览视频的网站 */ for(let i = 0; i < VideoSupport.length; i ++){ if( url.includes( VideoSupport[i][0].split("*")[0] ) ){ return { "state":true, "data":i }; break; } } return { "state":false, "data":-1 }; } function getWebContents(txt) { var links = []; var images = []; var content = ""; var outline = []; /* 获取所有链接 */ txt.replace(/]*href=['"]([^'"]+)['"][^>]*>/g, function(match, capture){ links.push(capture); }); /* 获取所有图片 */ txt.replace(/]*src=['"]([^'"]+)['"][^>]*>/g, function(match, capture){ images.push(capture); }); /* 删除多余空格和换行 */ content = txt.replace(/\s+/g, ' ').trim().replace(/(\r\n|\r|\n){2,}/g, '$1\n'); /* 获取文本,去掉特定标签 */ content = content.replace(/<\/div>/g, "\n") .replace(/<\/table>/g, "\n") .replace(/<\/h3>/g, "\n") .replace(/<\/p>/g, "

\n") .replace(/<\/li>/g, "\n") .replace(/.*?<\/script>/gis, "") .replace(/.*?<\/style>/gis, "") .replace(/.*?<\/nav>/gis, ""); content = content.replace(/<(?!\/?(a|img|code|pre)\b)[^>]+>/g, ''); /* 删除除了a和img以外的标签 */ /* 将换行符变为换行,删除多余的br */ content = content.replace(/\n/g,"
").replace(/(\s*)+/g, '
'); /* 获取大纲信息 */ try{ outline = getOutline(txt); } catch(e){ console.log("[ERROR] 大纲处理问题") } var final_data = {"link": links, "image": images, "content": content, "outline": outline}; return final_data; } function openReader(url) { /* 打开阅读器 */ /* 阅读器加载提示 */ var closeBtn = $("#userscript-closeBtn"); var previewReader = $("#userscript-webPreviewReader"); previewReader.html("

正在载入...
" + url + "

"); previewReader.fadeIn(100); closeBtn.fadeIn(100); /* 判断当前链接是支持预览的视频网站,并作出对应处理 */ var SoN = JudgeVideoSupport(url); if(SoN.state == true){ /* 被支持的视频网站的处理 */ var origUrl = url; var frameUrl = ""; url = url.replace(VideoSupport[SoN.data][0].split("*")[0], ""); url = url + "?#"; url = url.split("#")[0].split("?")[0]; url = url.replace(VideoSupport[SoN.data][0].split("*")[1], ""); frameUrl = VideoSupport[SoN.data][1].replace("*", url); previewReader.html(`
`); /* 淡入 */ $("#FadeInContainer").fadeIn(700); } else { /* 普通网站的处理 */ runAsync(url, "GET", "").then((result)=>{ return result; }).then(function(result){ /* 源数据处理(csdn存在利用img的onerror属性注入xss脚本的行为) */ result = result.replace(/]*src\s*=\s*["']{2}[^>]*>/gi, ''); /* 删除src为空的标签 */ result = result.replace(/]*)onerror\s*=\s*(['"]?[^'">]*['"]?)([^>]*)>/gi, ''); /* 删除所有img标签的onerror属性 */ /* 对指定网站进行内容过滤,指定元素获取 */ let orig_result_backup = result; const domain = url.split("/")[2]; if (contentEleSelList[domain]) { try { const selector = contentEleSelList[domain]; result = $(result).find(selector).html(); } catch (e) { console.log("[ERROR] 特定网站处理问题") } } if (!result) { result = orig_result_backup; } /* 用函数解析网页 */ let reslist = getWebContents(result); let linkhtml = "", imghtml = "", outlinehtml = ""; /* 处理链接列表 */ for(let i = 0; i < reslist.link.length; i ++){ let link_tmp = reslist.link[i]; if(link_tmp.includes("//")){ linkhtml += " 🔗 " + link_tmp + "
"; } } /* 处理图片列表 */ for(let i = 0; i < reslist.image.length; i ++){ imghtml += ""; } /* 处理大纲 */ for(let i = 0; i < reslist.outline.length; i ++){ let space = ""; for(let j = 1; j < reslist.outline[i].level; j ++){ space += "  "; } outlinehtml += space + "+ " + reslist.outline[i].title + "
" } /* 将所有结果添加进阅读器,并显示 */ previewReader.html(` `); /* 如果没有链接/图片,那么就隐藏 */ if(reslist.image.length == 0) { $(".ImageList").hide(); } if(reslist.link.length == 0) { $(".LinkList").hide(); } if(reslist.outline.length == 0) { $(".OutlineShow").hide(); } /* 淡入 */ $("#FadeInContainer").fadeIn(250); }); } /* 执行结束 */ } /* =============[ 搜索结果分析 ]============ */ /* * 自动判断当前元素下是否 具有搜索结果特征 * 解释:判断一个父元素下存在 大于等于5个的 具有相同class的 子元素。 * 该函数用于统计当前元素的子元素的各个class的数量,若其中存在一个class的数量大于5次,则判断为搜索结果 */ function checkSearchResults(parentElement) { var classList = []; var countList = []; for(let i = 0; i < parentElement.children.length; i ++) { var child = parentElement.children[i]; var childClass = child.classList; for(let j = 0; j < childClass.length; j ++) { if(classList.indexOf(childClass[j]) !== -1) { /* 对列表中的class出现次数进行计数 */ var p = classList.indexOf(childClass[j]); countList[p] += 1; } else { /* 对列表中未出现的class,插入列表 */ classList.push(childClass[j]); countList.push(0); } } } var countMax = Math.max.apply(null, countList); return (countMax >= 5); } /* 遍历元素 */ function traverseElements(element, callback) { if (!element || !element.children || element.children.length === 0) { return; } var returnCode = callback(element); if (returnCode) { return; } /* 如果返回值为true,则代表该元素已包含搜索结果,无需继续遍历 */ for (let i = 0; i < element.children.length; i++) { traverseElements(element.children[i], callback); } } /* * 运用上述的 遍历函数 和 分析判断函数 实现在满足要求的搜索结果旁插入“速览按钮” * 解释:遍历DOM,获取搜索列表,插入按钮 * 该函数是全程序 分析部分 的起始 */ function initAnalyze() { traverseElements(document.body, function(element) { var status = checkSearchResults(element); if(status) { console.log("存在搜索结果:", status); let resultItems = element.children; for(let i = 0; i < resultItems.length; i ++) { try { let resultItemLink = resultItems[i].getElementsByTagName("a")[0].href; let resultItemTitleEle = resultItems[i].getElementsByTagName("a")[0].parentNode; let resultItemText = resultItems[i].getElementsByTagName("a")[0].innerText; if(resultItemText.length <= 5 || !resultItemLink){ continue; } if(resultItemLink.includes("javascript:") && resultItemLink[0] == "j") { continue; } /* 向每一个搜索结果的标题部分添加按钮 */ let previewBtn = document.createElement("button"); let previewBtnImg = document.createElement("img"); previewBtn.setAttribute("class", "userscript-webPreviewBtn"); previewBtn.setAttribute("link-data", resultItemLink); previewBtnImg.setAttribute("src", iconImg); resultItemTitleEle.appendChild(previewBtn); previewBtn.appendChild(previewBtnImg); previewBtn.addEventListener("click", function(evt){ let linkData = previewBtn.getAttribute("link-data"); openReader(linkData); }, true); } catch(e) { console.log("[ERROR] ELE(" + i + ") \n" + e); } } return true; } else { return false; } }); } /* ===================================== */ /* 初始化 */ function init(){ /* 适配黑暗模式 */ if(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { /* 黑暗模式下的样式 */ GM_addStyle(` .userscript-webPreviewBtn{ user-select:none; background-color:#00390a55; color:#7edb7b; padding:6px 18px; font-weight:bold; line-height:16px; height:30px; margin-left:5px; border-radius:30px; border:1px solid #7edb7b; cursor:pointer; } .userscript-webPreviewBtn:hover{ background-color:#00390aAA; } .userscript-webPreviewBtn:active{ background-color:#7edb7b; } .userscript-webPreviewBtn img{ height:16px; } .userscript-closeBtn{ position:fixed; top:calc(8% + 5px); right:26px; z-index:100000; background:#7edb7b; color:#00390a; padding:8px 20px; margin:6px; border-radius:30px; font-weight:bold; border:0; border-bottom:1px solid #00390a; cursor:pointer; } .userscript-closeBtn:hover{ background:#76cd74; } .userscript-webPreviewReader{ font-size:medium; text-align:left; position:fixed; top:8vh; right:10px; bottom:0px; z-index:99999; width:35%; height:calc(100vh - 8%); min-width:340px; background:#1a1c19; border:1px solid #424940; color:#e2e3dd; overflow:hidden; box-shadow: 0 0 0 1px rgba(0,0,0,.1), 0 2px 4px 1px rgba(0,0,0,.18); border-radius:28px 28px 0px 0px; } .userscript-webPreviewReader img.error{ display:none; } .userscript-webPreviewReader .ShowList{ margin:0;padding:0;width:100%;cursor:pointer;color:#7edb7b; user-select:none; } .userscript-webPreviewReader .image{ height:85px; margin-bottom:8px; margin-right:5px; border-radius:15px; object-fit:contain; max-width:calc(100% - 20px); } .userscript-webPreviewReader .link{ text-decoration:none; color:#7edb7b!important; margin-left: 5px; } .userscript-webPreviewReader .link:hover{ text-decoration:underline; } .ImageList, .LinkList, .OutlineShow, .ContentShow{ padding:16px; margin:8px; background:#42494047; border-radius:30px; overflow:hidden; color:#d5e8cf; box-shadow:0 .5px 1.5px 0 rgba(0,0,0,.19),0 0 1px 0 rgba(0,0,0,.039); } .ContentShow img{ max-width:90%!important; position:relative!important; top:0!important; left:0!important; border-radius:10px; } .ContentShow img::after{ content: ""; display: block; clear: both; } .ContentShow a{ color:#7edb7b; text-decoration:underline 1px solid #386a1f; margin:0px 3px; } .ContentShow code { font-family: Consolas, Courier, Courier New, monospace; } .ContentShow pre { color:#dcdcdc; background:#1e201d; width:90%; padding:5px; margin:5px 0px; overflow-y:auto; height:fit-content; border:1px solid #424940; border-radius:5px;} .ContentShow code:not(pre code) { color:#e2e3dd; background:#42494047; border-radius:0.25rem; padding:.125rem .375rem; line-height:1.75; word-wrap:break-word; border:1px solid #424940; } .userscript-webPreviewReader #videoFrame{ width: calc(100% - 10px); height: calc(100% - 0px); border:1px solid #CCC; border-radius:30px; margin:5px; } .userscript-webPreviewReader #FadeInContainer { overflow-y:scroll; overflow-x:hidden; border-radius:15px 15px 0px 0px; width:100%; height:100%; } `); } else { /* 日间模式下的样式 */ GM_addStyle(` .userscript-webPreviewBtn{ user-select:none; background-color:#FFFFFFAA; color:#386a1f; padding:6px 18px; font-weight:bold; line-height:16px; height:30px; margin-left:5px; border-radius:30px; border:1px solid #285a0f; cursor:pointer; } .userscript-webPreviewBtn:hover{ background-color:#edf1e5; } .userscript-webPreviewBtn:active{ background-color:#d7e1cd; } .userscript-webPreviewBtn img{ height:16px; } .userscript-closeBtn{ position:fixed; top:calc(8% + 5px); right:26px; z-index:100000; background:#386a1f; color:#FFF; padding:8px 20px; margin:6px; border-radius:30px; font-weight:bold; border:0; border-bottom:1px solid #20460e; cursor:pointer; } .userscript-closeBtn:hover{ background:#487631; } .userscript-webPreviewReader{ font-size:medium; text-align:left; position:fixed; top:8vh; right:10px; bottom:0px; z-index:99999; width:35%; height:calc(100vh - 8%); min-width:340px; background:#fdfdf6; color:#131f0d; overflow:hidden; box-shadow: 0 0 0 1px rgba(0,0,0,.1), 0 2px 4px 1px rgba(0,0,0,.18); border-radius:28px 28px 0px 0px; } .userscript-webPreviewReader img.error{ display:none; } .userscript-webPreviewReader .ShowList{ margin:0;padding:0;width:100%;cursor:pointer;color:#386a1f; user-select:none; } .userscript-webPreviewReader .image{ height:85px; margin-bottom:8px; margin-right:5px; border-radius:15px; object-fit:contain; max-width:calc(100% - 20px); } .userscript-webPreviewReader .link{ text-decoration:none; color:#386a1f!important; margin-left: 5px; } .userscript-webPreviewReader .link:hover{ text-decoration:underline; } .ImageList, .LinkList, .OutlineShow, .ContentShow{ padding:16px; margin:8px; background:rgb(216,231,203); border-radius:30px; overflow:hidden; color:#131f0d; box-shadow:0 .5px 1.5px 0 rgba(0,0,0,.19),0 0 1px 0 rgba(0,0,0,.039); } .ContentShow img{ max-width:90%!important; position:relative!important; top:0!important; left:0!important; border-radius:10px; } .ContentShow img::after{ content: ""; display: block; clear: both; } .ContentShow a{ color:#386a1f; text-decoration:underline 1px solid #386a1f; margin:0px 3px; } .ContentShow code { font-family: Consolas, Courier, Courier New, monospace; } .ContentShow pre { color:#1a1c19; background:#eeeee8; width:90%; padding:5px; margin:5px 0px; overflow-y:auto; height:fit-content; border:1px solid #dee5d8; border-radius:5px;} .ContentShow code:not(pre code) { color:#1a1c19; background:#e2e3dd; border-radius:0.25rem; padding:.125rem .375rem; line-height:1.75; word-wrap:break-word; border:1px solid #dee5d8; } .userscript-webPreviewReader #videoFrame{ width: calc(100% - 10px); height: calc(100% - 0px); border:1px solid #CCC; border-radius:30px; margin:5px; } .userscript-webPreviewReader #FadeInContainer { overflow-y:scroll; overflow-x:hidden; border-radius:15px 15px 0px 0px; width:100%; height:100%; } `); } /* 将必要组件在页面加载时插入DOM */ /* 阅读器 */ if( !$("#userscript-webPreviewReader").length ){ var $previewReader = $('
', { class: 'userscript-webPreviewReader', style: 'display:none;', id: 'userscript-webPreviewReader' }).appendTo('body'); /* 阅读器关闭按钮 */ var $closeBtn = $('